/*
 * Copyright (c) 2019. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
 * Morbi non lorem porttitor neque feugiat blandit. Ut vitae ipsum eget quam lacinia accumsan.
 * Etiam sed turpis ac ipsum condimentum fringilla. Maecenas magna.
 * Proin dapibus sapien vel ante. Aliquam erat volutpat. Pellentesque sagittis ligula eget metus.
 * Vestibulum commodo. Ut rhoncus gravida arcu.
 */

package com.darly.dlcommon.common.bolts.links;

import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;

import com.darly.dlcommon.common.dlog.DLog;

import org.json.JSONArray;
import org.json.JSONObject;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

/**
 * Bolts raises events to the application by sending local broadcasts.
 * Application integrated to Bolts can listen to events by registering BroadcastReceiver with
 * intent filter {@link #MEASUREMENT_EVENT_NOTIFICATION_NAME}. When receive the event,
 * a String field {@link #MEASUREMENT_EVENT_NAME_KEY} and A Bundle field @{link #MEASUREMENT_EVENT_ARGS_KEY}
 * in Intent extra in indicate the event name and arguments with this event.
 */
public class MeasurementEvent {
    /**
     * The action of the broadcast intent that represents Bolts measurement event.
     */
    public static final String MEASUREMENT_EVENT_NOTIFICATION_NAME = "com.parse.bolts.measurement_event";

    /**
     * The field name in the broadcast intent extra bundle that represents Bolts measurement event name.
     */
    public static final String MEASUREMENT_EVENT_NAME_KEY = "event_name";

    /**
     * The field name in the broadcast intent extra bundle that represents Bolts measurement event arguments.
     */
    public static final String MEASUREMENT_EVENT_ARGS_KEY = "event_args";

    // Events
    /**
     * The name for event of navigating out to other apps. Event raised in navigation methods, e.g.
     * {@link bolts.AppLinkNavigation#navigateInBackground(Context, String)}
     **/
    public static final String APP_LINK_NAVIGATE_OUT_EVENT_NAME = "al_nav_out";

    /**
     * The name for event of handling incoming applink intent. Event raised in
     * {@link AppLinks#getTargetUrlFromInboundIntent(Context, Intent)}
     **/
    public static final String APP_LINK_NAVIGATE_IN_EVENT_NAME = "al_nav_in";

    /**
     * Broadcast Bolts measurement event.
     * Bolts raises events to the application with this method by sending
     * {@link #MEASUREMENT_EVENT_NOTIFICATION_NAME} broadcast.
     *
     * @param context          the context of activity or application who is going to send the event. required.
     * @param name             the event name that is going to be sent. required.
     * @param intent           the intent that carries the logging data in its extra bundle and data url. optional.
     * @param extraLoggingData other logging data to be sent in events argument. optional.
     */
    static void sendBroadcastEvent(
            Context context,
            String name,
            Intent intent,
            Map<String, String> extraLoggingData) {
        Bundle logData = new Bundle();
        if (intent != null) {
            Bundle applinkData = AppLinks.getAppLinkData(intent);
            if (applinkData != null) {
                logData = getApplinkLogData(context, name, applinkData, intent);
            } else {
                Uri intentUri = intent.getData();
                if (intentUri != null) {
                    logData.putString("intentData", intentUri.toString());
                }
                Bundle intentExtras = intent.getExtras();
                if (intentExtras != null) {
                    for (String key : intentExtras.keySet()) {
                        Object o = intentExtras.get(key);
                        String logValue = objectToJSONString(o);
                        logData.putString(key, logValue);
                    }
                }
            }
        }

        if (extraLoggingData != null) {
            for (String key : extraLoggingData.keySet()) {
                logData.putString(key, extraLoggingData.get(key));
            }
        }
        MeasurementEvent event = new MeasurementEvent(context, name, logData);
        event.sendBroadcast();
    }

    private Context appContext;
    private String name;
    private Bundle args;

    private MeasurementEvent(Context context, String eventName, Bundle eventArgs) {
        appContext = context.getApplicationContext();
        name = eventName;
        args = eventArgs;
    }

    private void sendBroadcast() {
        if (name == null) {
            DLog.d(getClass().getName(), "Event name is required");
        }
        try {
            Class<?> clazz = Class.forName("android.support.v4.content.LocalBroadcastManager");
            Method methodGetInstance = clazz.getMethod("getInstance", Context.class);
            Method methodSendBroadcast = clazz.getMethod("sendBroadcast", Intent.class);
            Object localBroadcastManager = methodGetInstance.invoke(null, appContext);
            Intent event = new Intent(MEASUREMENT_EVENT_NOTIFICATION_NAME);
            event.putExtra(MEASUREMENT_EVENT_NAME_KEY, name);
            event.putExtra(MEASUREMENT_EVENT_ARGS_KEY, args);
            methodSendBroadcast.invoke(localBroadcastManager, event);
        } catch (Exception e) {
            DLog.d(getClass().getName(),
                    "LocalBroadcastManager in android support library is required to raise bolts event.");
        }
    }

    private static Bundle getApplinkLogData(Context context, String eventName, Bundle appLinkData, Intent applinkIntent) {
        Bundle logData = new Bundle();
        ComponentName resolvedActivity = applinkIntent.resolveActivity(context.getPackageManager());

        if (resolvedActivity != null) {
            logData.putString("class", resolvedActivity.getShortClassName());
        }

        if (APP_LINK_NAVIGATE_OUT_EVENT_NAME.equals(eventName)) {
            if (resolvedActivity != null) {
                logData.putString("package", resolvedActivity.getPackageName());
            }
            if (applinkIntent.getData() != null) {
                logData.putString("outputURL", applinkIntent.getData().toString());
            }
            if (applinkIntent.getScheme() != null) {
                logData.putString("outputURLScheme", applinkIntent.getScheme());
            }
        } else if (APP_LINK_NAVIGATE_IN_EVENT_NAME.equals(eventName)) {
            if (applinkIntent.getData() != null) {
                logData.putString("inputURL", applinkIntent.getData().toString());
            }
            if (applinkIntent.getScheme() != null) {
                logData.putString("inputURLScheme", applinkIntent.getScheme());
            }
        }

        for (String key : appLinkData.keySet()) {
            Object o = appLinkData.get(key);
            if (o instanceof Bundle) {
                for (String subKey : ((Bundle) o).keySet()) {
                    String logValue = objectToJSONString(((Bundle) o).get(subKey));
                    if (key.equals("referer_app_link")) {
                        if (subKey.equalsIgnoreCase("url")) {
                            logData.putString("refererURL", logValue);
                            continue;
                        } else if (subKey.equalsIgnoreCase("app_name")) {
                            logData.putString("refererAppName", logValue);
                            continue;
                        } else if (subKey.equalsIgnoreCase("package")) {
                            logData.putString("sourceApplication", logValue);
                            continue;
                        }
                    }
                    logData.putString(key + "/" + subKey, logValue);
                }
            } else {
                String logValue = objectToJSONString(o);
                if (key.equals("target_url")) {
                    Uri targetURI = Uri.parse(logValue);
                    logData.putString("targetURL", targetURI.toString());
                    logData.putString("targetURLHost", targetURI.getHost());
                    continue;
                }
                logData.putString(key, logValue);
            }
        }
        return logData;
    }

    private static String objectToJSONString(Object o) {
        if (o == null) {
            return null;
        }
        if (o instanceof JSONArray || o instanceof JSONObject) {
            return o.toString();
        }

        try {
            if (o instanceof Collection) {
                return (new JSONArray((Collection) o)).toString();
            }
            if (o instanceof Map) {
                return (new JSONObject((Map) o)).toString();
            }
            return o.toString();
        } catch (Exception ignored) {
        }
        return null;
    }
}
